package com.wangfugui.apprentice.common.util;

import io.minio.BucketExistsArgs;
import io.minio.CopyObjectArgs;
import io.minio.CopySource;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import io.netty.buffer.ByteBuf;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


@Slf4j
public class MinIoClient implements IOssClient {

    private final MinioClient client;

    public MinIoClient(MinioClient client) {
        this.client = client;
    }

    /**
     * upload file
     *
     * @param stream     文件流
     * @param objectName 文件名
     * @param basePath   路径,如果带/，会被替换为-
     * @return
     */
    public String upload(InputStream stream, String objectName, String basePath) {
       return this.upload(stream, objectName, basePath, BucketNameEnum.SAFETY);
    }

    @Override
    public String upload(InputStream stream, String originalFilename, String basePath, BucketNameEnum bucketName) {
        String bucket = bucketName.getName();
        String fileName = MinIoUtil.joinObjectName(basePath, originalFilename);
        try {
            return upload0(stream, fileName, bucket);
        } catch (IOException e) {
            return null; //兼容原有OssClient接口
        }
    }

    /**
     * 上传一个文件
     *
     * @param stream     stream
     * @param objectName objectName
     * @param bucket     桶(文件夹)
     * @throws IOException IOException
     */
    protected String upload0(InputStream stream, String objectName, String bucket) throws IOException {
        try {
            log.info("upload bucket:{}, object:{}", bucket, objectName);
            int objectSize = -1; //对象大小不限制
            int partSize = 10485760; //达到这个大小之后，在服务端写文件时自动切分文件
            bucket = renameBucket(bucket);
            PutObjectArgs args = PutObjectArgs.builder().bucket(bucket).object(objectName).stream(stream, objectSize, partSize).build();
            ObjectWriteResponse response = client.putObject(args);
            if (response != null) {
                return getPresignedObjectUrl(bucket, objectName, GetPresignedObjectUrlArgs.DEFAULT_EXPIRY_TIME);
            }
            throw new IOException("fail to get response for:" + bucket + ":" + objectName);
        } catch (Exception e) {
            log.error("fail to upload file. bucket:{}, file:{}", bucket, objectName, e);
            throw new IOException(e);
        }
    }

    /**
     * 下载文件
     * <p>
     * 1qzb
     *
     * @param fileUrl
     * @return ByteBuf对象
     */
    public ByteBuf download(String fileUrl) {
        return MinIoUtil.streamToByteBuf(downloadStream(fileUrl));
    }

    /**
     * 下载文件
     *
     * @param fileUrl
     * @return stream流
     */
    public InputStream downloadStream(String fileUrl) {
        S3UrlParser.S3Location url = S3UrlParser.parseUrl(fileUrl);
        try {
            return download0(url.getBucket(), url.getObjectName());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 下载一个文件
     *
     * @param bucket     桶
     * @param objectName 文件名
     * @return 流
     */
    public InputStream download0(String bucket, String objectName) throws IOException {
        bucket = renameBucket(bucket);
        try {
            GetObjectArgs args = GetObjectArgs.builder().bucket(bucket).object(objectName).build();
            return client.getObject(args);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 创建一个桶
     *
     * @param bucket 桶
     */
    public void createBucket(String bucket) throws IOException {
        bucket = renameBucket(bucket);
        try {
            BucketExistsArgs existsArgs = BucketExistsArgs.builder().bucket(bucket).build();
            boolean found = client.bucketExists(existsArgs);
            if (!found) {
                MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucket).build();
                client.makeBucket(makeBucketArgs);
            }
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 列出所有的桶
     *
     * @return 所有的桶
     */
    public List<String> listBuckets() throws IOException {
        try {
            List<Bucket> list = client.listBuckets();
            List<String> names = new ArrayList<>();
            list.forEach(b -> {
                names.add(b.name());
            });
            return names;
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 列出一个桶中的所有文件和目录
     *
     * @param bucket 桶
     * @return 桶中的所有文件和目录
     */
    public List<FileInfo> listFiles(String bucket) {
        bucket = renameBucket(bucket);
        ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucket).recursive(true).build();
        Iterable<Result<Item>> results = client.listObjects(args);

        List<FileInfo> infos = new ArrayList<>();
        results.forEach(r -> {
            FileInfo info = new FileInfo();
            try {
                Item item = r.get();
                info.setFileName(item.objectName());
                info.setIsDir(item.isDir());
                infos.add(info);
            } catch (Exception e) {
                //throw new IOException(e); XXX 此处未抛异常，可能引起问题
                log.error("fail to list files", e);
            }
        });
        return infos;
    }

    /**
     * 删除一个桶
     *
     * @param bucket 桶
     */
    public void deleteBucket(String bucket) throws IOException {
        try {
            RemoveBucketArgs args = RemoveBucketArgs.builder().bucket(bucket).build();
            client.removeBucket(args);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 删除一个对象
     *
     * @param bucket     桶
     * @param objectName 对象名
     */
    public void deleteObject(String bucket, String objectName) throws IOException {
        bucket = renameBucket(bucket);
        try {
            RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(bucket).object(objectName).build();
            client.removeObject(args);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }


    /**
     * 复制文件
     *
     * @param sourceBucket 源桶
     * @param sourceObject 源对象
     * @param targetBucket 目标桶
     * @param targetObject 目标对象
     * @throws IOException
     */
    public void copyObject(String sourceBucket, String sourceObject, String targetBucket, String targetObject) throws IOException {
        try {
            this.createBucket(targetBucket);
            CopySource source = CopySource.builder().bucket(sourceBucket).object(sourceObject).build();
            CopyObjectArgs args = CopyObjectArgs.builder().bucket(targetBucket).object(targetObject).source(source).build();
            client.copyObject(args);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 获取文件信息
     *
     * @param bucket     桶
     * @param objectName 对象名
     * @return 对象信息
     * @throws IOException
     */
    public String getObjectInfo(String bucket, String objectName) throws IOException {
        try {
            bucket = renameBucket(bucket);
            StatObjectArgs args = StatObjectArgs.builder().bucket(bucket).object(objectName).build();
            return client.statObject(args).toString();
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载，即使其所在的存储桶是私有的。
     *
     * @param bucket     桶
     * @param objectName 对象名
     * @param expires    过期时间
     * @return url
     * @throws IOException
     */
    public String getPresignedObjectUrl(String bucket, String objectName, Integer expires) throws IOException {
        bucket = renameBucket(bucket);
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().bucket(bucket).object(objectName).expiry(expires).method(Method.GET).build();
        try {
            return client.getPresignedObjectUrl(args);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * 获取minio中所有的文件
     *
     * @return minio中所有的文件
     * @throws IOException
     */
    public List<FileInfo> listAllFile() throws IOException {
        List<String> list = this.listBuckets();
        List<FileInfo> fileInfos = new ArrayList<>();
        for (String bucketName : list) {
            fileInfos.addAll(this.listFiles(bucketName));
        }
        return fileInfos;
    }

    private String renameBucket(String bucket) {
        return bucket.replaceAll("/", "-");
    }
}

